<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>HashMap-JDK7源码讲解 | 风祈的时光录</title><meta name="keywords" content="Java集合，HashMap。"><meta name="author" content="youthlql"><meta name="copyright" content="youthlql"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="HashMap-JDK7源码讲解。">
<meta property="og:type" content="article">
<meta property="og:title" content="HashMap-JDK7源码讲解">
<meta property="og:url" content="https://imlql.cn/post/f1f58db2.html">
<meta property="og:site_name" content="风祈的时光录">
<meta property="og:description" content="HashMap-JDK7源码讲解。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://upyunimg.imlql.cn/lql_static@latest/logo/java.png">
<meta property="article:published_time" content="2020-11-01T02:21:58.000Z">
<meta property="article:modified_time" content="2022-07-24T11:02:09.866Z">
<meta property="article:author" content="youthlql">
<meta property="article:tag" content="Java集合">
<meta property="article:tag" content="HashMap">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://upyunimg.imlql.cn/lql_static@latest/logo/java.png"><link rel="shortcut icon" href="https://npm.elemecdn.com/youthlql@1.0.8/blog/favicon.png"><link rel="canonical" href="https://imlql.cn/post/f1f58db2"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//hm.baidu.com"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://npm.elemecdn.com/@fortawesome/fontawesome-free@6/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://npm.elemecdn.com/@fancyapps/ui/dist/fancybox.css" media="print" onload="this.media='all'"><script>var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?f693ff99cc7e613b88cf5b729a14b48b";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'HashMap-JDK7源码讲解',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2022-07-24 19:02:09'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="https://npm.elemecdn.com/lql_static@latest/butterfly_static/css/ali_icon.css"><link rel="stylesheet" href="https://npm.elemecdn.com/lql_static@latest/butterfly_static/css/2-24-mogai.css"><meta name="generator" content="Hexo 5.4.0"><link rel="alternate" href="/atom.xml" title="风祈的时光录" type="application/atom+xml">
</head><body><div id="web_bg"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/avatar/2.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data is-center"><div class="data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">37</div></a></div><div class="data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">21</div></a></div><div class="data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">13</div></a></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://upyunimg.imlql.cn/lql_static@latest/logo/java.png')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">风祈的时光录</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw iconfont icon-shouyex"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw iconfont icon-zuixinwenzhang_huaban"></i><span> 找文章</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/categories/"><i class="fa-fw iconfont icon-fenlei1"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw iconfont icon-biaoqian1"></i><span> 标签</span></a></li><li><a class="site-page child" href="/archives/"><i class="fa-fw iconfont icon-shijianzhou"></i><span> 归档</span></a></li></ul></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">HashMap-JDK7源码讲解</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2020-11-01T02:21:58.000Z" title="发表于 2020-11-01 10:21:58">2020-11-01</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2022-07-24T11:02:09.866Z" title="更新于 2022-07-24 19:02:09">2022-07-24</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java%E9%9B%86%E5%90%88/">Java集合</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Java%E9%9B%86%E5%90%88/HashMap/">HashMap</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">6.3k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>25分钟</span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><blockquote>
<p> 本文基于版本 <code>JDK 1.7</code>，即 <code>Java 7</code>。</p>
</blockquote>
<h1 id="HashMap用法"><a href="#HashMap用法" class="headerlink" title="HashMap用法"></a>HashMap用法</h1><h2 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function">V <span class="title">get</span><span class="params">(Object key)</span></span>; <span class="comment">// 获得指定键的值</span></span><br><span class="line"><span class="function">V <span class="title">put</span><span class="params">(K key, V value)</span></span>;  <span class="comment">// 添加键值对</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">putAll</span><span class="params">(Map&lt;? extends K, ? extends V&gt; m)</span></span>;  <span class="comment">// 将指定Map中的键值对 复制到 此Map中</span></span><br><span class="line"><span class="function">V <span class="title">remove</span><span class="params">(Object key)</span></span>;  <span class="comment">// 删除该键值对</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">containsKey</span><span class="params">(Object key)</span></span>; <span class="comment">// 判断是否存在该键的键值对；是 则返回true</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">containsValue</span><span class="params">(Object value)</span></span>;  <span class="comment">// 判断是否存在该值的键值对；是 则返回true</span></span><br><span class="line"> </span><br><span class="line"><span class="function">Set&lt;K&gt; <span class="title">keySet</span><span class="params">()</span></span>;  <span class="comment">// 单独抽取key序列，将所有key生成一个Set</span></span><br><span class="line"><span class="function">Collection&lt;V&gt; <span class="title">values</span><span class="params">()</span></span>;  <span class="comment">// 单独value序列，将所有value生成一个Collection</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">clear</span><span class="params">()</span></span>; <span class="comment">// 清除哈希表中的所有键值对</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">size</span><span class="params">()</span></span>;  <span class="comment">// 返回哈希表中所有 键值对的数量 = 数组中的键值对 + 链表中的键值对</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">()</span></span>; <span class="comment">// 判断HashMap是否为空；size == 0时 表示为 空 </span></span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.*;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.*;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Author</span>: youthlql-吕</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span>: 2020/10/11 21:05</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;</span></span><br><span class="line"><span class="comment"> * 功能描述:</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">test</span> </span>&#123;</span><br><span class="line">    <span class="comment">//声明1个 HashMap的对象</span></span><br><span class="line">    <span class="keyword">static</span> Map&lt;String, Integer&gt; map = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//向HashMap添加数据（成对 放入 键 - 值对）</span></span><br><span class="line">        map.put(<span class="string">&quot;Java&quot;</span>, <span class="number">1</span>);</span><br><span class="line">        map.put(<span class="string">&quot;hadoop&quot;</span>, <span class="number">2</span>);</span><br><span class="line">        map.put(<span class="string">&quot;产品经理&quot;</span>, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//获取 HashMap 的某个数据</span></span><br><span class="line">        System.out.println(<span class="string">&quot;key = 产品经理时的值为：&quot;</span> + map.get(<span class="string">&quot;产品经理&quot;</span>));</span><br><span class="line"></span><br><span class="line">        System.out.println();</span><br><span class="line">        System.out.println(<span class="string">&quot;------------------下面是遍历-------------------&quot;</span>);</span><br><span class="line">        test();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取HashMap的全部数据：遍历HashMap</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 方法1：获得key-value的Set集合 再遍历</span></span><br><span class="line">        System.out.println(<span class="string">&quot;------------方法1------------&quot;</span>);</span><br><span class="line">        <span class="comment">// 1、获得key-value对（Entry）的Set集合</span></span><br><span class="line">        Set&lt;Map.Entry&lt;String, Integer&gt;&gt; entrySet = map.entrySet();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2、遍历Set集合，从而获取key-value</span></span><br><span class="line">        <span class="keyword">for</span> (Map.Entry&lt;String, Integer&gt; entry : entrySet) &#123;</span><br><span class="line">            System.out.print(entry.getKey());</span><br><span class="line">            System.out.println(entry.getValue());</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;----------&quot;</span>);</span><br><span class="line">        <span class="comment">// 2.1、通过迭代器：先获得key-value对（Entry）的Iterator，再循环遍历</span></span><br><span class="line">        Iterator iter1 = entrySet.iterator();</span><br><span class="line">        <span class="keyword">while</span> (iter1.hasNext()) &#123;</span><br><span class="line">            <span class="comment">// 遍历时，需先获取entry，再分别获取key、value</span></span><br><span class="line">            Map.Entry entry = (Map.Entry) iter1.next();</span><br><span class="line">            System.out.print((String) entry.getKey());</span><br><span class="line">            System.out.println((Integer) entry.getValue());</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 方法2：获得key的Set集合 再遍历</span></span><br><span class="line">        System.out.println(<span class="string">&quot;------------方法2------------&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 获得key的Set集合</span></span><br><span class="line">        Set&lt;String&gt; keySet = map.keySet();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 遍历Set集合，从而获取key，再获取value</span></span><br><span class="line">        <span class="comment">// 2.1 通过for循环</span></span><br><span class="line">        <span class="keyword">for</span> (String key : keySet) &#123;</span><br><span class="line">            System.out.print(key);</span><br><span class="line">            System.out.println(map.get(key));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;----------&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.2 通过迭代器：先获得key的Iterator，再循环遍历</span></span><br><span class="line">        Iterator iter2 = keySet.iterator();</span><br><span class="line">        String key = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">while</span> (iter2.hasNext()) &#123;</span><br><span class="line">            key = (String) iter2.next();</span><br><span class="line">            System.out.print(key);</span><br><span class="line">            System.out.println(map.get(key));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 方法3：获得value的Set集合 再遍历</span></span><br><span class="line">        System.out.println(<span class="string">&quot;------------方法3------------&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1. 获得value的Set集合</span></span><br><span class="line">        Collection valueSet = map.values();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 遍历Set集合，从而获取value</span></span><br><span class="line">        <span class="comment">// 2.1 获得values 的Iterator</span></span><br><span class="line">        Iterator iter3 = valueSet.iterator();</span><br><span class="line">        <span class="comment">// 2.2 通过遍历，直接获取value</span></span><br><span class="line">        <span class="keyword">while</span> (iter3.hasNext()) &#123;</span><br><span class="line">            System.out.println(iter3.next());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 对于遍历方式，推荐使用针对 key-value对（Entry）的方式：效率高</span></span><br><span class="line"><span class="comment"> * 原因：</span></span><br><span class="line"><span class="comment"> * 1. 对于 遍历keySet 、valueSet，实质上 = 遍历了2次：</span></span><br><span class="line"><span class="comment"> *    第一次转为iterator迭代器遍历、</span></span><br><span class="line"><span class="comment"> *    第二次从HashMap中取出key和value操作</span></span><br><span class="line"><span class="comment"> * 2. 对于遍历entrySet，实质只遍历了1次，也就是获取存储实体Entry（存储了key 和 value ）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>



<p><strong>结果</strong></p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">key &#x3D; 产品经理时的值为：3</span><br><span class="line"></span><br><span class="line">------------------下面是遍历-------------------</span><br><span class="line">------------方法1------------</span><br><span class="line">Java1</span><br><span class="line">hadoop2</span><br><span class="line">产品经理3</span><br><span class="line">----------</span><br><span class="line">Java1</span><br><span class="line">hadoop2</span><br><span class="line">产品经理3</span><br><span class="line">------------方法2------------</span><br><span class="line">Java1</span><br><span class="line">hadoop2</span><br><span class="line">产品经理3</span><br><span class="line">----------</span><br><span class="line">Java1</span><br><span class="line">hadoop2</span><br><span class="line">产品经理3</span><br><span class="line">------------方法3------------</span><br><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td></tr></table></figure>





<h1 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h1><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>1、HashMap是散列表的一种，HashMap本身采用数组来进行储存。同时HashMap采用拉链法来解决hash冲突，拉链法就是通过链表来解决hash冲突。所以说整体上来看，HashMap采用的数据结构 = <strong>数组（主） + 单链表（副）</strong></p>
<p>大致是这样的一个结构</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0001.png">

<ul>
<li>每个链表就算哈希表的桶（bucket）</li>
<li>链表的节点值就算一个键值对</li>
</ul>
<h2 id="重要参数介绍"><a href="#重要参数介绍" class="headerlink" title="重要参数介绍"></a>重要参数介绍</h2><p><strong>构造函数源码</strong></p>
<p>先贴一下，后面参数介绍会使用到。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity, <span class="keyword">float</span> loadFactor)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Illegal initial capacity: &quot;</span> +</span><br><span class="line">                                              initialCapacity);</span><br><span class="line">       <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">           initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">       <span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">&quot;Illegal load factor: &quot;</span> +</span><br><span class="line">                                              loadFactor);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">this</span>.loadFactor = loadFactor;</span><br><span class="line">       threshold = initialCapacity;</span><br><span class="line">       init();</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(Map&lt;? extends K, ? extends V&gt; m)</span> </span>&#123;</span><br><span class="line">       <span class="keyword">this</span>(Math.max((<span class="keyword">int</span>) (m.size() / DEFAULT_LOAD_FACTOR) + <span class="number">1</span>,</span><br><span class="line">                     DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);</span><br><span class="line">       inflateTable(threshold);</span><br><span class="line"></span><br><span class="line">       putAllForCreate(m);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>



<blockquote>
<p>下面的代码有些顺序和源码可能不太一样，仅仅是顺序，笔者是为了更好的阅读体验</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HashMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">extends</span> <span class="title">AbstractMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">    <span class="keyword">implements</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">Cloneable</span>, <span class="title">Serializable</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    名词介绍：</span></span><br><span class="line"><span class="comment">    1、容量（capacity）： HashMap中数组【强调一下是数组，不是元素个数】的长度</span></span><br><span class="line"><span class="comment">    2、容量范围：必须是2的幂并且小于最大容量（2的30次方）</span></span><br><span class="line"><span class="comment">    3、初始容量 = 哈希表创建时的容量</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">//默认初始容量 = 哈希表创建时的容量。默认容量 = 16 = 1&lt;&lt;4 = 00001中的1向左移4位 = 十进制的2^4=16</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">1</span> &lt;&lt; <span class="number">4</span>; <span class="comment">// aka 16</span></span><br><span class="line">	</span><br><span class="line">    <span class="comment">// 最大容量 =  2的30次方（若传入的容量过大，将被最大值替换）</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> MAXIMUM_CAPACITY = <span class="number">1</span> &lt;&lt; <span class="number">30</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    1、加载因子(Load factor)：HashMap在其容量自动增加前的一种尺度。</span></span><br><span class="line"><span class="comment">    2、加载因子越大、填满的元素越多 = 空间利用率高、但hash冲突的机会加大、查找效率变低（因为链表变长了）</span></span><br><span class="line"><span class="comment">    3、加载因子越小、填满的元素越少 = 空间利用率小、hash冲突的机会减小、查找效率高（链表不长）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 实际加载因子</span></span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">float</span> loadFactor;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 默认加载因子 = 0.75</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> DEFAULT_LOAD_FACTOR = <span class="number">0.75f</span>;</span><br><span class="line">	</span><br><span class="line">    <span class="comment">//就是上面说的数组，hashmap用Entry数组储存k-v键值对</span></span><br><span class="line">    <span class="keyword">transient</span> Entry&lt;K,V&gt;[] table = (Entry&lt;K,V&gt;[]) EMPTY_TABLE;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> Entry&lt;?,?&gt;[] EMPTY_TABLE = &#123;&#125;;</span><br><span class="line"></span><br><span class="line">	</span><br><span class="line">    <span class="comment">// HashMap的大小，即HashMap中存储的键值对的数量。注意：和容量区分开，容量是数组Entry的长度</span></span><br><span class="line">    <span class="keyword">transient</span> <span class="keyword">int</span> size;</span><br><span class="line">	</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    1、扩容阈值（threshold）：当哈希表的大小【就是上面的size】 ≥ 扩容阈值时，就会扩容哈希表</span></span><br><span class="line"><span class="comment">    （即扩充HashMap的容量） </span></span><br><span class="line"><span class="comment">    2、扩容 = 对哈希表进行resize操作（即重建内部数据结构），从而哈希表将具有大约两倍的桶数</span></span><br><span class="line"><span class="comment">    3、扩容阈值 = 容量 x 加载因子</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">int</span> threshold;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>





<h2 id="数组-Entry"><a href="#数组-Entry" class="headerlink" title="数组-Entry"></a>数组-Entry</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * Entry类实现了Map.Entry接口</span></span><br><span class="line"><span class="comment"> * 即 实现了getKey()、getValue()、equals(Object o)和hashCode()等方法</span></span><br><span class="line"><span class="comment">**/</span>  </span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; <span class="keyword">implements</span> <span class="title">Map</span>.<span class="title">Entry</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> K key;  <span class="comment">// 键</span></span><br><span class="line">    V value;  <span class="comment">// 值</span></span><br><span class="line">    Entry&lt;K,V&gt; next; <span class="comment">// 指向下一个节点 ，也是一个Entry对象，从而形成解决hash冲突的单链表</span></span><br><span class="line">    <span class="keyword">int</span> hash;  <span class="comment">// hash值</span></span><br><span class="line">  </span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * 构造方法，创建一个Entry </span></span><br><span class="line"><span class="comment">     * 参数：哈希值h，键值k，值v、下一个节点n </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    Entry(<span class="keyword">int</span> h, K k, V v, Entry&lt;K,V&gt; n) &#123;  </span><br><span class="line">        value = v;  </span><br><span class="line">        next = n;  </span><br><span class="line">        key = k;  </span><br><span class="line">        hash = h;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 返回 与 此项 对应的键</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> K <span class="title">getKey</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">        <span class="keyword">return</span> key;  </span><br><span class="line">    &#125;  </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 返回 与 此项 对应的值</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">getValue</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">        <span class="keyword">return</span> value;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> V <span class="title">setValue</span><span class="params">(V newValue)</span> </span>&#123;  </span><br><span class="line">        V oldValue = value;  </span><br><span class="line">        value = newValue;  </span><br><span class="line">        <span class="keyword">return</span> oldValue;  </span><br><span class="line">    &#125;  </span><br><span class="line">    </span><br><span class="line">   <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * equals（）</span></span><br><span class="line"><span class="comment">     * 作用：判断2个Entry是否相等，必须key和value都相等，才返回true  </span></span><br><span class="line"><span class="comment">     */</span> </span><br><span class="line">      <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object o)</span> </span>&#123;  </span><br><span class="line">        <span class="keyword">if</span> (!(o <span class="keyword">instanceof</span> Map.Entry))  </span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;  </span><br><span class="line">        Map.Entry e = (Map.Entry)o;  </span><br><span class="line">        Object k1 = getKey();  </span><br><span class="line">        Object k2 = e.getKey();  </span><br><span class="line">        <span class="keyword">if</span> (k1 == k2 || (k1 != <span class="keyword">null</span> &amp;&amp; k1.equals(k2))) &#123;  </span><br><span class="line">            Object v1 = getValue();  </span><br><span class="line">            Object v2 = e.getValue();  </span><br><span class="line">            <span class="keyword">if</span> (v1 == v2 || (v1 != <span class="keyword">null</span> &amp;&amp; v1.equals(v2)))  </span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">    </span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * hashCode（） </span></span><br><span class="line"><span class="comment">     */</span> </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hashCode</span><span class="params">()</span> </span>&#123; </span><br><span class="line">        <span class="keyword">return</span> Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">final</span> String <span class="title">toString</span><span class="params">()</span> </span>&#123;  </span><br><span class="line">        <span class="keyword">return</span> getKey() + <span class="string">&quot;=&quot;</span> + getValue();  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * 当向HashMap中添加元素时，即调用put(k,v)时， </span></span><br><span class="line"><span class="comment">     * 对已经在HashMap中k位置进行v的覆盖时，会调用此方法 </span></span><br><span class="line"><span class="comment">     * 此处没做任何处理 </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">recordAccess</span><span class="params">(HashMap&lt;K,V&gt; m)</span> </span>&#123;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * 当从HashMap中删除了一个Entry时，会调用该函数 </span></span><br><span class="line"><span class="comment">     * 此处没做任何处理 </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">recordRemoval</span><span class="params">(HashMap&lt;K,V&gt; m)</span> </span>&#123;  </span><br><span class="line">    &#125; </span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>HashMap</code>中的数组元素 &amp; 链表节点 采用 <code>Entry</code>类实现</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0001.png">

<p>1、一个正方形代表一个Entry对象，同时也代表一个键值对。</p>
<p>2、即 <code>HashMap</code>的本质 = 1个存储<code>Entry</code>类对象的数组 + 多个单链表</p>
<p>3、Entry对象本质 = 1个映射（键 - 值对），属性包括：键（<code>key</code>）、值（<code>value</code>）、 下个节点( <code>next</code>) = 单链表的指针 = 也是一个<code>Entry</code>对象，用于解决<code>hash</code>冲突</p>
<h1 id="构造函数源码"><a href="#构造函数源码" class="headerlink" title="构造函数源码"></a>构造函数源码</h1><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HashMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">      <span class="keyword">extends</span> <span class="title">AbstractMap</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;</span></span><br><span class="line"><span class="class">      <span class="keyword">implements</span> <span class="title">Map</span>&lt;<span class="title">K</span>,<span class="title">V</span>&gt;, <span class="title">Cloneable</span>, <span class="title">Serializable</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略上节阐述的参数</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">      构造函数1：默认构造函数（无参）</span></span><br><span class="line"><span class="comment">      加载因子和容量为默认，分别是0.75和16</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         实际上是调用构造函数3：指定&quot;容量大小&quot;和&quot;加载因子&quot;的构造函数</span></span><br><span class="line"><span class="comment">         传入的指定容量和加载因子均为默认</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">        <span class="keyword">this</span>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">      构造函数2：指定&quot;容量大小&quot;的构造函数</span></span><br><span class="line"><span class="comment">      加载因子是默认的0.75 、容量为指定大小</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 实际上是调用的也是构造函数3，只是在传入的加载因子参数为默认加载因子</span></span><br><span class="line">        <span class="keyword">this</span>(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">      构造函数3：指定“容量大小”和“加载因子”的构造函数</span></span><br><span class="line"><span class="comment">      加载因子和容量都是程序员自己指定</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(<span class="keyword">int</span> initialCapacity, <span class="keyword">float</span> loadFactor)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// HashMap的最大容量只能是MAXIMUM_CAPACITY，哪怕传入的 &gt; 最大容量</span></span><br><span class="line">        <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">            initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置加载因子</span></span><br><span class="line">        <span class="keyword">this</span>.loadFactor = loadFactor;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">        设置扩容阈值 = 初始容量</span></span><br><span class="line"><span class="comment">        1、注意：此处不是真正的阈值，仅是为了接收参数初始容量大小（capacity）、加载因子(Load factor)，</span></span><br><span class="line"><span class="comment">        并没有真正初始化哈希表，即初始化存储数组table</span></span><br><span class="line"><span class="comment">        2、真正初始化哈希表（初始化存储数组table）是在第1次添加键值对时，即第1次调用put()时，下面会</span></span><br><span class="line"><span class="comment">        详细说明。</span></span><br><span class="line"><span class="comment">        */</span></span><br><span class="line">        threshold = initialCapacity;   </span><br><span class="line"></span><br><span class="line">        init(); <span class="comment">// 一个空方法用于未来的子对象扩展</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">      构造函数4：包含“子Map”的构造函数</span></span><br><span class="line"><span class="comment">      即构造出来的HashMap包含传入Map参数</span></span><br><span class="line"><span class="comment">      加载因子和容量均为默认</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">HashMap</span><span class="params">(Map&lt;? extends K, ? extends V&gt; m)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置容量大小和加载因子为默认值</span></span><br><span class="line">        <span class="keyword">this</span>(Math.max((<span class="keyword">int</span>) (m.size() / DEFAULT_LOAD_FACTOR) + <span class="number">1</span>,</span><br><span class="line">                DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 该方法用于初始化数组和阈值</span></span><br><span class="line">        inflateTable(threshold);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将传入的子Map中的全部元素逐个添加到HashMap中</span></span><br><span class="line">        putAllForCreate(m);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h1 id="put-源码"><a href="#put-源码" class="headerlink" title="put()源码"></a>put()源码</h1><h2 id="put"><a href="#put" class="headerlink" title="put()"></a>put()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">put</span><span class="params">(K key, V value)</span> </span>&#123;</span><br><span class="line">    	    	</span><br><span class="line">    	<span class="comment">/*  ①</span></span><br><span class="line"><span class="comment">    	1、若哈希表未初始化（即 table为空)，则调用inflateTable方法，使用构造函数时设置的阈值</span></span><br><span class="line"><span class="comment">    	(即初始容量)初始化数组table </span></span><br><span class="line"><span class="comment">    	*/</span></span><br><span class="line">        <span class="keyword">if</span> (table == EMPTY_TABLE) &#123;</span><br><span class="line">            inflateTable(threshold);</span><br><span class="line">        &#125;</span><br><span class="line">    	<span class="comment">/*  ②</span></span><br><span class="line"><span class="comment">    	1、判断key是否为空值null</span></span><br><span class="line"><span class="comment">		2、若key == null，则调用putForNullKey方法，putForNullKey方法最终将该键-值存放到数组</span></span><br><span class="line"><span class="comment">		table中的第1个位置，即table[0]。本质：key = Null时，hash值 = 0，故存放到table[0]中）</span></span><br><span class="line"><span class="comment">		该位置永远只有1个value，新传进来的value会覆盖旧的value</span></span><br><span class="line"><span class="comment">		3、k != null往下走</span></span><br><span class="line"><span class="comment">    	*/</span></span><br><span class="line">        <span class="keyword">if</span> (key == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">return</span> putForNullKey(value);</span><br><span class="line">    	<span class="comment">//下面就是k != null的情况</span></span><br><span class="line">    </span><br><span class="line">    	<span class="comment">//计算key的hash值</span></span><br><span class="line">        <span class="keyword">int</span> hash = hash(key);</span><br><span class="line">    	<span class="comment">//根据hash值最终获得key对应存放的数组Table中位置(也就是数组下标)</span></span><br><span class="line">        <span class="keyword">int</span> i = indexFor(hash, table.length);</span><br><span class="line">    	</span><br><span class="line">    	<span class="comment">/*  ③</span></span><br><span class="line"><span class="comment">    	1、通过遍历以该数组元素为头结点的链表，逐个判断是否发生hash冲突，同时判断该key对应的值是</span></span><br><span class="line"><span class="comment">    	否已存在</span></span><br><span class="line"><span class="comment">    	*/</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[i]; e != <span class="keyword">null</span>; e = e.next) &#123;</span><br><span class="line">            Object k;</span><br><span class="line">            <span class="comment">/* ④</span></span><br><span class="line"><span class="comment">            1、如果发生了hash冲突，且key也相等。则用新value替换旧value(此时说明发生了更新的情况)，</span></span><br><span class="line"><span class="comment">            注意这里强调的是发生了hash冲突并且key也相等。</span></span><br><span class="line"><span class="comment">            */</span></span><br><span class="line">            <span class="keyword">if</span> (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))) &#123;</span><br><span class="line">                V oldValue = e.value;</span><br><span class="line">                e.value = value;</span><br><span class="line">                e.recordAccess(<span class="keyword">this</span>);</span><br><span class="line">                <span class="keyword">return</span> oldValue;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">		<span class="comment">//修改次数+1</span></span><br><span class="line">        modCount++;</span><br><span class="line">    	<span class="comment">/* ⑤</span></span><br><span class="line"><span class="comment">    	1、走到这一步有两种情况</span></span><br><span class="line"><span class="comment">    	  1-1、没有发生hash冲突</span></span><br><span class="line"><span class="comment">    	  1-2、发生了hash冲突，但是没有在链表中找到key一样的键</span></span><br><span class="line"><span class="comment">    	2、接着准备采用头插法，插入链表的头部(也就是数组里面)</span></span><br><span class="line"><span class="comment">    	*/</span></span><br><span class="line">        addEntry(hash, key, value, i);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<h2 id="inflateTable"><a href="#inflateTable" class="headerlink" title="inflateTable()"></a>inflateTable()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">inflateTable</span><span class="params">(<span class="keyword">int</span> toSize)</span> </span>&#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">	将传入的容量大小转化为：&gt;传入容量大小的最小的2的次幂，即如果传入的是容量大小是18，那么转化后，</span></span><br><span class="line"><span class="comment">	初始化容量大小为32（即2的5次幂）</span></span><br><span class="line"><span class="comment">	*/</span>	</span><br><span class="line">    <span class="keyword">int</span> capacity = roundUpToPowerOf2(toSize);</span><br><span class="line">    </span><br><span class="line">	<span class="comment">//重新计算阈值 threshold = 容量 * 加载因子  </span></span><br><span class="line">    threshold = (<span class="keyword">int</span>) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    使用计算后的初始容量（已经是2的次幂） 初始化数组table（作为数组长度）即 哈希表的容量大小 = </span></span><br><span class="line"><span class="comment">    数组大小（长度）</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    table = <span class="keyword">new</span> Entry[capacity];</span><br><span class="line">    initHashSeedAsNeeded(capacity);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="roundUpToPowerOf2"><a href="#roundUpToPowerOf2" class="headerlink" title="roundUpToPowerOf2()"></a>roundUpToPowerOf2()</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">roundUpToPowerOf2</span><span class="params">(<span class="keyword">int</span> number)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//若容量超过了最大值，初始化容量设置为最大值 ；否则，设置为&gt;传入容量大小的最小的2的次幂</span></span><br><span class="line">    <span class="keyword">return</span> number &gt;= MAXIMUM_CAPACITY</span><br><span class="line">    ? MAXIMUM_CAPACITY</span><br><span class="line">    : (number &gt; <span class="number">1</span>) ? Integer.highestOneBit((number - <span class="number">1</span>) &lt;&lt; <span class="number">1</span>) : <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="putForNullKey"><a href="#putForNullKey" class="headerlink" title="putForNullKey()"></a>putForNullKey()</h2><p>当 key ==null时，将该 key-value 的存储位置规定为数组table 中的第1个位置，即table [0]</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> V <span class="title">putForNullKey</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">/*</span></span><br><span class="line"><span class="comment">	1、遍历以table[0]为首的链表，寻找是否存在key==null对应的键值对</span></span><br><span class="line"><span class="comment">	   1-1、若有：则用新value替换旧value；同时返回旧的value值。</span></span><br><span class="line"><span class="comment">	*/</span></span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[<span class="number">0</span>]; e != <span class="keyword">null</span>; e = e.next) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e.key == <span class="keyword">null</span>) &#123;</span><br><span class="line">            V oldValue = e.value;</span><br><span class="line">            e.value = value;</span><br><span class="line">            e.recordAccess(<span class="keyword">this</span>);</span><br><span class="line">            <span class="keyword">return</span> oldValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    modCount++;</span><br><span class="line">   </span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    1、若无key==null的键，那么调用addEntry（），将空键和对应的值封装到Entry中，并放到table[0]中</span></span><br><span class="line"><span class="comment">    2、能走到这里的情况就是table[0]位置之前没有元素，这一次是第一次添加key为null的元素</span></span><br><span class="line"><span class="comment">    3、k == null已经是固定放在了table[0]的位置，不会有链表存在，只会有一个元素。</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    addEntry(<span class="number">0</span>, <span class="keyword">null</span>, value, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从此处可以看出：</p>
<ul>
<li><code>HashMap</code>的键<code>key</code> 可为<code>null</code>（区别于 <code>HashTable</code>的<code>key</code> 不可为<code>null</code>）</li>
<li><code>HashMap</code>的键<code>key</code> 可为<code>null</code>且只能为1个，但值<code>value</code>可为null且为多个</li>
</ul>
<h2 id="hash"><a href="#hash" class="headerlink" title="hash()"></a>hash()</h2><blockquote>
<p>这个方法比较重要，1.7和1.8改动的比较大</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 1、该函数在JDK7和8中的实现不同，但原理一样 = 扰动函数 = 使得根据key生成的</span></span><br><span class="line"><span class="comment">   * 哈希码（hash值）分布更加均匀、更具备随机性，避免出现hash值冲突（即指不同key但生成同1个hash值）</span></span><br><span class="line"><span class="comment">   * 2、JDK 1.7 做了9次扰动处理 = 4次位运算 + 5次异或运算</span></span><br><span class="line"><span class="comment">   * 3、JDK 1.8 简化了扰动函数 = 只做了2次扰动 = 1次位运算 + 1次异或运算</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="function"><span class="keyword">final</span> <span class="keyword">int</span> <span class="title">hash</span><span class="params">(Object k)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">int</span> h = hashSeed;</span><br><span class="line">      <span class="keyword">if</span> (<span class="number">0</span> != h &amp;&amp; k <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">      	<span class="keyword">return</span> sun.misc.Hashing.stringHash32((String) k);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      h ^= k.hashCode();</span><br><span class="line"><span class="comment">//4次位运算 + 5次异或运算</span></span><br><span class="line">      h ^= (h &gt;&gt;&gt; <span class="number">20</span>) ^ (h &gt;&gt;&gt; <span class="number">12</span>);</span><br><span class="line">      <span class="keyword">return</span> h ^ (h &gt;&gt;&gt; <span class="number">7</span>) ^ (h &gt;&gt;&gt; <span class="number">4</span>);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>



<h2 id="indexFor"><a href="#indexFor" class="headerlink" title="indexFor()"></a>indexFor()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//这里h &amp; (length-1)的意思就是hash值与数组长度取模。只是因为数组长度是特殊的2的幂，</span></span><br><span class="line"><span class="comment">//所以这个等价关系刚好成立</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexFor</span><span class="params">(<span class="keyword">int</span> h, <span class="keyword">int</span> length)</span> </span>&#123;</span><br><span class="line">       <span class="comment">// assert Integer.bitCount(length) == 1 : &quot;length must be a non-zero power of 2&quot;;</span></span><br><span class="line">       <span class="keyword">return</span> h &amp; (length-<span class="number">1</span>);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>



<h2 id="addEntry"><a href="#addEntry" class="headerlink" title="addEntry()"></a>addEntry()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">//作用：添加键值对（Entry）到HashMap中。参数3:插入数组table的索引位置,也就是数组下标</span></span><br><span class="line"><span class="comment">//注意：JDK7是先扩容再插入</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>&#123;</span><br><span class="line">	<span class="comment">/*</span></span><br><span class="line"><span class="comment">	1、插入前，先判断容量是否足够</span></span><br><span class="line"><span class="comment">          1.1、若不足够，则进行扩容（2倍）、重新计算Hash值、重新计算存储数组下标</span></span><br><span class="line"><span class="comment">	*/</span></span><br><span class="line">       <span class="keyword">if</span> ((size &gt;= threshold) &amp;&amp; (<span class="keyword">null</span> != table[bucketIndex])) &#123;</span><br><span class="line">           resize(<span class="number">2</span> * table.length);</span><br><span class="line">           hash = (<span class="keyword">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line">           bucketIndex = indexFor(hash, table.length);</span><br><span class="line">       &#125;</span><br><span class="line">	<span class="comment">// 1.2、若容量足够，则创建1个新的数组元素（Entry） 并放入到数组中</span></span><br><span class="line">       createEntry(hash, key, value, bucketIndex);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>



<h1 id="扩容源码"><a href="#扩容源码" class="headerlink" title="扩容源码"></a>扩容源码</h1><p>下面的也是JDK7扩容的步骤，接着上面的<code>addEntry()</code></p>
<h2 id="resize"><a href="#resize" class="headerlink" title="resize()"></a>resize()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 作用：当大小不足时（size &gt; 阈值，注意是size，不是容量），则扩容（扩到2倍）</span></span><br><span class="line"><span class="comment">   */</span> </span><br><span class="line">   <span class="function"><span class="keyword">void</span> <span class="title">resize</span><span class="params">(<span class="keyword">int</span> newCapacity)</span> </span>&#123;  </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1、保存旧数组（old table） </span></span><br><span class="line">    Entry[] oldTable = table;  </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2、保存旧容量（old capacity ），即数组长度</span></span><br><span class="line">    <span class="keyword">int</span> oldCapacity = oldTable.length; </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3、若旧容量已经是系统默认最大容量了，那么将阈值设置成整型的最大值，返回   </span></span><br><span class="line">    <span class="keyword">if</span> (oldCapacity == MAXIMUM_CAPACITY) &#123;  </span><br><span class="line">        threshold = Integer.MAX_VALUE;  </span><br><span class="line">        <span class="keyword">return</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 4、根据新容量（2倍容量）新建1个数组，即newTable </span></span><br><span class="line">    Entry[] newTable = <span class="keyword">new</span> Entry[newCapacity];  </span><br><span class="line"></span><br><span class="line">    <span class="comment">//5、将旧数组上的数据（键值对）转移到新table中，从而完成扩容</span></span><br><span class="line">    transfer(newTable, initHashSeedAsNeeded(newCapacity));</span><br><span class="line">       </span><br><span class="line">    <span class="comment">// 6、新数组table引用到HashMap的table属性上</span></span><br><span class="line">    table = newTable;</span><br><span class="line">       </span><br><span class="line">    <span class="comment">// 7、重新设置阈值 </span></span><br><span class="line">    threshold = (<span class="keyword">int</span>)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure>





<h2 id="transfer"><a href="#transfer" class="headerlink" title="transfer()"></a>transfer()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 作用：将旧数组上的数据（键值对）转移到新table中，从而完成扩容</span></span><br><span class="line"><span class="comment">   * 过程：按旧链表的正序遍历链表、在新链表的头部依次插入。但是这样会导致扩容完成后，链表逆序</span></span><br><span class="line"><span class="comment">   */</span> 	</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Entry[] newTable, <span class="keyword">boolean</span> rehash)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> newCapacity = newTable.length;</span><br><span class="line">        <span class="comment">//通过遍历 旧数组，将旧数组上的数据（键值对）转移到新数组中</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="keyword">null</span> != e) &#123;</span><br><span class="line">                <span class="comment">/*</span></span><br><span class="line"><span class="comment">                 1、遍历以该数组元素为首的链表</span></span><br><span class="line"><span class="comment">                 2、转移链表时，因是单链表，故要保存下1个结点，否则转移后链表会断开</span></span><br><span class="line"><span class="comment">                 */</span></span><br><span class="line">                Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">                <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                    e.hash = <span class="keyword">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                &#125;</span><br><span class="line">                 <span class="comment">//重新计算每个元素的存储位置</span></span><br><span class="line">                <span class="keyword">int</span> i = indexFor(e.hash, newCapacity);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">//这个地方暂时先放着，后面讲死循环链表的时候会讲到</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                <span class="comment">//讲当前元素，赋给新数组的对应下标位置。</span></span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                <span class="comment">// 访问下1个Entry链上的元素，如此不断循环，直到遍历完该链表上的所有节点</span></span><br><span class="line">                e = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>大概画了一下图：</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0002.png"/>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0003.png">



<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0004.png">

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0005.png">



<p>1、在扩容<code>resize（）</code>过程中，在将旧数组上的数据转移到新数组上时，转移操作就是按旧链表的正序遍历链表、在新链表的头部依次插入，即在转移数据、扩容后，容易出现链表逆序的情况。</p>
<p>2、设重新计算存储位置后不变，即扩容前 1-&gt;2-&gt;3，扩容后 = 3-&gt;2-&gt;1</p>
<p>3、此时若（多线程）并发执行 put（）操作，一旦出现扩容情况，则 <strong>容易出现环形链表</strong>，从而在获取数据、遍历链表时形成死循环（Infinite Loop），即<strong>线程不安全</strong>。</p>
<h2 id="createEntry"><a href="#createEntry" class="headerlink" title="createEntry()"></a>createEntry()</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">createEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>&#123;</span><br><span class="line">        Entry&lt;K,V&gt; e = table[bucketIndex];</span><br><span class="line">        table[bucketIndex] = <span class="keyword">new</span> Entry&lt;&gt;(hash, key, value, e);</span><br><span class="line">        size++; <span class="comment">//这里才进行size++</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>举个例子说明流程：</p>
<p>1、假设hashmap中容量为16，加载因为为0.75 = 12。</p>
<p>2、此时hashmap中有11个元素，也就是size == 11，在你添加第12个元素时。看代码，此时size还是11，所以并不会扩容。只有在你调用完<code>createEntry()</code>,<code>size++</code>执行完毕后，size变成12。</p>
<p>3、在添加第13个元素时，才会进入if逻辑里进行先扩容。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> ((size &gt;= threshold) &amp;&amp; (<span class="keyword">null</span> != table[bucketIndex])) &#123;</span><br><span class="line">           resize(<span class="number">2</span> * table.length);</span><br><span class="line">           hash = (<span class="keyword">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line">           bucketIndex = indexFor(hash, table.length);</span><br><span class="line">       &#125;</span><br><span class="line">       createEntry(hash, key, value, bucketIndex);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>





<h1 id="扩容出现的死循环链表"><a href="#扩容出现的死循环链表" class="headerlink" title="扩容出现的死循环链表"></a>扩容出现的死循环链表</h1><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 作用：将旧数组上的数据（键值对）转移到新table中，从而完成扩容</span></span><br><span class="line"><span class="comment">   * 过程：按旧链表的正序遍历链表、在新链表的头部依次插入。但是这样会导致扩容完成后，链表逆序</span></span><br><span class="line"><span class="comment">   */</span> 	</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Entry[] newTable, <span class="keyword">boolean</span> rehash)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> newCapacity = newTable.length;</span><br><span class="line">        <span class="comment">//通过遍历 旧数组，将旧数组上的数据（键值对）转移到新数组中</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="keyword">null</span> != e) &#123;</span><br><span class="line">                <span class="comment">/*</span></span><br><span class="line"><span class="comment">                 1、遍历以该数组元素为首的链表</span></span><br><span class="line"><span class="comment">                 2、转移链表时，因是单链表，故要保存下1个结点，否则转移后链表会断开</span></span><br><span class="line"><span class="comment">                 */</span></span><br><span class="line">                Entry&lt;K,V&gt; next = e.next; <span class="comment">//pos_1</span></span><br><span class="line">                <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                    e.hash = <span class="keyword">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                &#125;</span><br><span class="line">                 <span class="comment">//重新计算每个元素的存储位置</span></span><br><span class="line">                <span class="keyword">int</span> i = indexFor(e.hash, newCapacity);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">//这个地方暂时先放着，后面讲死循环链表的时候会讲到</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                <span class="comment">//讲当前元素，赋给新数组的对应下标位置。</span></span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                <span class="comment">// 访问下1个Entry链上的元素，如此不断循环，直到遍历完该链表上的所有节点</span></span><br><span class="line">                e = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<h2 id="前置条件"><a href="#前置条件" class="headerlink" title="前置条件"></a>前置条件</h2><p>1、为了演示方便，初始状态时，hashmap容量为2，加载因子为默认的0.75.</p>
<h2 id="步骤1"><a href="#步骤1" class="headerlink" title="步骤1"></a>步骤1</h2><p><strong>hashmap初始状态</strong></p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0006.png">



<p>1、此时只有一个元素，扩容阈值为2*0.75 = 1.5。</p>
<p>2、此时假设有两个线程，线程a和线程b同时put，并且都没有进入到addEntry()方法里的if逻辑【因为此时size都没有++，size == 1   1 &lt; 1.5  所以if判断不成立。】。两个线程都准备同时调用createEntry()方法。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addEntry</span><span class="params">(<span class="keyword">int</span> hash, K key, V value, <span class="keyword">int</span> bucketIndex)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> ((size &gt;= threshold) &amp;&amp; (<span class="keyword">null</span> != table[bucketIndex])) &#123;</span><br><span class="line">           resize(<span class="number">2</span> * table.length);</span><br><span class="line">           hash = (<span class="keyword">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line">           bucketIndex = indexFor(hash, table.length);</span><br><span class="line">       &#125;</span><br><span class="line">       createEntry(hash, key, value, bucketIndex);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<p>3、线程a put的是 e3 = &lt;k3,v3&gt;。线程b put的是e2 = &lt;k2,v2&gt;。两个都调用了<code>createEntry()</code>方法。</p>
<h2 id="步骤2"><a href="#步骤2" class="headerlink" title="步骤2"></a>步骤2</h2><p><strong>两个线程调用完毕之后，hashmap目前是这样的。</strong></p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0007.png">



<p>此时size==3。下次再进行put的时候，<strong>addEntry()方法里的if判断就会成立</strong>。</p>
<h2 id="步骤3"><a href="#步骤3" class="headerlink" title="步骤3"></a>步骤3</h2><p>1、接着，又来了两个线程，线程1和线程2。【假设线程1put的是e1，线程2put的是e0。其实也不用管它们两put的是谁】</p>
<p>2、两个线程都同时调用resize()方法，新数组已经扩容完毕，准备转移旧数组上的数据到新数组里。也就是准备调用resize()里的下面这个方法。</p>
<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">&#x2F;&#x2F;5、将旧数组上的数据（键值对）转移到新table中，从而完成扩容</span><br><span class="line">    transfer(newTable, initHashSeedAsNeeded(newCapacity));</span><br></pre></td></tr></table></figure>

<p>3、来看下此时内存里的状态</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0008.png"/>

<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0009.png">

<h2 id="步骤4"><a href="#步骤4" class="headerlink" title="步骤4"></a>步骤4</h2><p>来看下源码【上面源码里有注释，这里把注释去掉】</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Entry[] newTable, <span class="keyword">boolean</span> rehash)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> newCapacity = newTable.length;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="keyword">null</span> != e) &#123;</span><br><span class="line">				<span class="comment">//pos_1</span></span><br><span class="line">                Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">                <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                    e.hash = <span class="keyword">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">int</span> i = indexFor(e.hash, newCapacity);   </span><br><span class="line">                <span class="comment">//pos_2</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                <span class="comment">//pos_3</span></span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                <span class="comment">//pos_4</span></span><br><span class="line">                e = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<p>1、假设线程1<strong>执行完</strong>代码pos_1位置后，暂时挂起。此时e == e2  e.next == e3</p>
<p>2、线程2直接<strong>扩容完毕</strong>，那么完成后的状态是这样【假设e2和e3还是hash到同一个位置】</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0010.png">

<p>3、线程1还是原来的状态</p>
<blockquote>
<p>强调一点：线程2已经扩容完毕</p>
</blockquote>
<h2 id="步骤5"><a href="#步骤5" class="headerlink" title="步骤5"></a>步骤5</h2><p>目前两个线程里的新数组是这样的</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0011.png">

<p>为了方便后面观看，我画成这样。</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0012.png">



<h2 id="步骤6"><a href="#步骤6" class="headerlink" title="步骤6"></a>步骤6</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Entry[] newTable, <span class="keyword">boolean</span> rehash)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> newCapacity = newTable.length;</span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="keyword">null</span> != e) &#123;</span><br><span class="line">				<span class="comment">//pos_1</span></span><br><span class="line">                Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">                <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                    e.hash = <span class="keyword">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">int</span> i = indexFor(e.hash, newCapacity);   </span><br><span class="line">                <span class="comment">//pos_2</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                <span class="comment">//pos_3</span></span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                <span class="comment">//pos_4</span></span><br><span class="line">                e = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>之前说过：假设线程1<strong>执行完</strong>代码pos_1位置后，暂时挂起。此时e == e2   e.next == e3【也就是next == e3】</p>
</blockquote>
<p>1、线程1唤醒后，继续执行pos_2，pos_3，pos_4</p>
<p>2、执行pos_2：意思是e2的next指针指向了线程1的新hash表【也就是newTable1】，因为newTable1是新的所以为null，</p>
<p>所以e2.next = null。</p>
<p>3、执行pos_3：newTable1[3] = e2;</p>
<p>4、执行pos_4： e = e3;</p>
<p>也就变成了下面这个样子。</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0013.png">





<h2 id="步骤7"><a href="#步骤7" class="headerlink" title="步骤7"></a>步骤7</h2><p>1、线程1继续执行循环</p>
<blockquote>
<p>注意之前强调过线程2已经扩容完毕，那么table就已经被指向了newTable2，也就是说第二次循环时，线程1所循环的table变量就是newTable2</p>
</blockquote>
<p>2、</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">transfer</span><span class="params">(Entry[] newTable, <span class="keyword">boolean</span> rehash)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> newCapacity = newTable.length;</span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="keyword">null</span> != e) &#123;</span><br><span class="line">				<span class="comment">//pos_1</span></span><br><span class="line">                Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">                <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                    e.hash = <span class="keyword">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">int</span> i = indexFor(e.hash, newCapacity);   </span><br><span class="line">                <span class="comment">//pos_2</span></span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                <span class="comment">//pos_3</span></span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                <span class="comment">//pos_4</span></span><br><span class="line">                e = next;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>1、执行pos_1：此时e == e3，那么next就是 e3.next，此时next == e2；</p>
<p>2、执行pos_2：经过第一轮循环，newTable1[3] == e2。那么执行完这行代码后，e3.next还是等于e2【相当于没执行】</p>
<p>3、执行pos_3：newTable1[3] == e3。</p>
<p>4、执行pos_4：e = e2</p>
<p>执行完，变成这样。</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0014.png">





<h2 id="步骤8"><a href="#步骤8" class="headerlink" title="步骤8"></a>步骤8</h2><p>线程1执行第三次循环</p>
<p>1、执行pos_1：next = e2.next得到  next == null。</p>
<p>2、执行pos_2： <code>e.next = newTable[i]</code>     e2.next == newTable1[3]。也就是相当于 e2.next == e3</p>
<p>3、执行pos_3： newTable[i] = e得到  newTable1[3] == e2</p>
<img src= "" data-lazy-src="https://upyunimg.imlql.cn/youthlql@1.0.8/Java_collection/HashMap/JDK7/0015.png">

<p>这样就形成了循环链表，再get()数据就会陷入死循环。</p>
<h1 id="get-源码"><a href="#get-源码" class="headerlink" title="get()源码"></a>get()源码</h1><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">   <span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(Object key)</span> </span>&#123;  </span><br><span class="line"></span><br><span class="line">    <span class="comment">//当key==null时，则到table[0]为头结点的链表去寻找对应 key == null的键</span></span><br><span class="line">    <span class="keyword">if</span> (key == <span class="keyword">null</span>)  </span><br><span class="line">        <span class="keyword">return</span> getForNullKey(); </span><br><span class="line"></span><br><span class="line">    <span class="comment">//当key ≠ null时，去获得对应值 </span></span><br><span class="line">    Entry&lt;K,V&gt; entry = getEntry(key);</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span> == entry ? <span class="keyword">null</span> : entry.getValue();  </span><br><span class="line">&#125;  </span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> V <span class="title">getForNullKey</span><span class="params">()</span> </span>&#123;  </span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (size == <span class="number">0</span>) &#123;  </span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line"></span><br><span class="line">        <span class="comment">// 遍历以table[0]为头结点的链表，寻找 key==null 对应的值</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[<span class="number">0</span>]; e != <span class="keyword">null</span>; e = e.next) &#123;  </span><br><span class="line"></span><br><span class="line">            <span class="comment">// 从table[0]中取key==null的value值 </span></span><br><span class="line">            <span class="keyword">if</span> (e.key == <span class="keyword">null</span>)  </span><br><span class="line">                <span class="keyword">return</span> e.value; </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">final</span> Entry&lt;K,V&gt; <span class="title">getEntry</span><span class="params">(Object key)</span> </span>&#123;  </span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (size == <span class="number">0</span>) &#123;  </span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line"></span><br><span class="line">        <span class="comment">//根据key值，通过hash（）计算出对应的hash值</span></span><br><span class="line">        <span class="keyword">int</span> hash = (key == <span class="keyword">null</span>) ? <span class="number">0</span> : hash(key);  </span><br><span class="line"></span><br><span class="line">        <span class="comment">//根据hash值计算出对应的数组下标,遍历以该数组下标的数组元素为头结点的链表所有节点，</span></span><br><span class="line">        <span class="comment">//寻找该key对应的值</span></span><br><span class="line">        <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[indexFor(hash, table.length)];  e != <span class="keyword">null</span>;  e = e.next) &#123;  </span><br><span class="line"></span><br><span class="line">            Object k;  </span><br><span class="line">            <span class="comment">// 若 hash值和key 相等，则证明该Entry = 我们要的键值对</span></span><br><span class="line">            <span class="keyword">if</span> (e.hash == hash &amp;&amp;  </span><br><span class="line">                ((k = e.key) == key || (key != <span class="keyword">null</span> &amp;&amp; key.equals(k))))  </span><br><span class="line">                <span class="keyword">return</span> e;  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line"></span><br></pre></td></tr></table></figure>

</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">youthlql</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://imlql.cn/post/f1f58db2.html">https://imlql.cn/post/f1f58db2.html</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://imlql.cn" target="_blank">风祈的时光录</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Java%E9%9B%86%E5%90%88/">Java集合</a><a class="post-meta__tags" href="/tags/HashMap/">HashMap</a></div><div class="post_share"></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="https://npm.elemecdn.com/youthlql@1.0.8/blog/wechat.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="https://npm.elemecdn.com/youthlql@1.0.8/blog/wechat.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="https://npm.elemecdn.com/youthlql@1.0.8/blog/alipay.jpg" target="_blank"><img class="post-qr-code-img" src= "" data-lazy-src="https://npm.elemecdn.com/youthlql@1.0.8/blog/alipay.jpg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/post/cbc5672a.html"><img class="prev-cover" src= "" data-lazy-src="https://upyunimg.imlql.cn/lql_static@latest/logo/java.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">HashMap-JDK8源码讲解及常见面试题</div></div></a></div><div class="next-post pull-right"><a href="/post/92c4503d.html"><img class="next-cover" src= "" data-lazy-src="https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Java并发体系-第四阶段-AQS源码解读-[1]</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/post/cbc5672a.html" title="HashMap-JDK8源码讲解及常见面试题"><img class="cover" src= "" data-lazy-src="https://upyunimg.imlql.cn/lql_static@latest/logo/java.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-11-01</div><div class="title">HashMap-JDK8源码讲解及常见面试题</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#HashMap%E7%94%A8%E6%B3%95"><span class="toc-text">HashMap用法</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%B8%B8%E7%94%A8API"><span class="toc-text">常用API</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84"><span class="toc-text">数据结构</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AE%80%E4%BB%8B"><span class="toc-text">简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%87%8D%E8%A6%81%E5%8F%82%E6%95%B0%E4%BB%8B%E7%BB%8D"><span class="toc-text">重要参数介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%95%B0%E7%BB%84-Entry"><span class="toc-text">数组-Entry</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E6%BA%90%E7%A0%81"><span class="toc-text">构造函数源码</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#put-%E6%BA%90%E7%A0%81"><span class="toc-text">put()源码</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#put"><span class="toc-text">put()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#inflateTable"><span class="toc-text">inflateTable()</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#roundUpToPowerOf2"><span class="toc-text">roundUpToPowerOf2()</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#putForNullKey"><span class="toc-text">putForNullKey()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#hash"><span class="toc-text">hash()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#indexFor"><span class="toc-text">indexFor()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#addEntry"><span class="toc-text">addEntry()</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%89%A9%E5%AE%B9%E6%BA%90%E7%A0%81"><span class="toc-text">扩容源码</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#resize"><span class="toc-text">resize()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#transfer"><span class="toc-text">transfer()</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#createEntry"><span class="toc-text">createEntry()</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%89%A9%E5%AE%B9%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AD%BB%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8"><span class="toc-text">扩容出现的死循环链表</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E7%BD%AE%E6%9D%A1%E4%BB%B6"><span class="toc-text">前置条件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A41"><span class="toc-text">步骤1</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A42"><span class="toc-text">步骤2</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A43"><span class="toc-text">步骤3</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A44"><span class="toc-text">步骤4</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A45"><span class="toc-text">步骤5</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A46"><span class="toc-text">步骤6</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A47"><span class="toc-text">步骤7</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%AD%A5%E9%AA%A48"><span class="toc-text">步骤8</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#get-%E6%BA%90%E7%A0%81"><span class="toc-text">get()源码</span></a></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By youthlql</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral" target="_blank" rel="noopener" class="one-pan-link-mark"><img style="position:relative;top:-3px; " src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/upyun/logo.png" align="absmiddle" width="60px" height="30px"></a><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn"><img class="icp-icon" src= "" data-lazy-src="https://npm.elemecdn.com/lql_static@latest/logo/icp.png"><span>鄂ICP备19028890号</span></a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">简</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">本地搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://npm.elemecdn.com/@fancyapps/ui/dist/fancybox.umd.js"></script><script src="https://npm.elemecdn.com/instant.page/instantpage.js" type="module"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script defer src="https://npm.elemecdn.com/jquery@latest/dist/jquery.min.js"></script></div></body></html>